package org.rainwalk.bill.api.formatter;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatter;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatters;
import org.rainwalk.bill.api.formatter.annotation.MoneyFormatter;
import org.rainwalk.bill.api.formatter.annotation.TimeFormatter;
import org.rainwalk.bill.api.model.Response;
import org.rainwalk.bill.model.enums.DescValueEnum;
import org.rainwalk.bill.util.MoneyUtil;
import org.rainwalk.bill.util.TimeUtil;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.BiFunction;

@Order(10)
@Aspect
@Component
@Slf4j
public class ConvertDelegate {

    private static final String SUFFIX = "Formatter";

    private static final String DEFAULT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";


    @Around("@annotation(org.rainwalk.bill.api.formatter.annotation.EnumFormatters) ||" +
            "@annotation(org.rainwalk.bill.api.formatter.annotation.EnumFormatter) ||" +
            "@annotation(org.rainwalk.bill.api.formatter.annotation.MoneyFormatter) ||" +
            "@annotation(org.rainwalk.bill.api.formatter.annotation.TimeFormatter)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed();

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();

        result = dealWithEnumConverts(method.getAnnotation(EnumFormatters.class), result);
        result = dealWithEnumConvert(method.getAnnotation(EnumFormatter.class), result);
        result = dealWithMoneyConvert(method.getAnnotation(MoneyFormatter.class), result);
        result = dealWithTimeConvert(method.getAnnotation(TimeFormatter.class), result);

        return result;
    }

    private Object dealWithTimeConvert(TimeFormatter timeFormatter, Object result) {
        if (timeFormatter == null) {
            return result;
        }

        if (timeFormatter.fields().length == 0) {
            return result;
        }

        return convert(result, timeFormatter, this::dealWithTimeConvertInternal);
    }

    private Map<String, Object> dealWithTimeConvertInternal(TimeFormatter timeFormatter, Map<String, Object> result) {
        for (String field : timeFormatter.fields()) {
            if (StrUtil.isBlank(field)) {
                continue;
            }

            String[] fields = field.split("\\|");
            Object value = result.get(fields[0]);
            if (value == null) {
                continue;
            }

            if(!(value instanceof Integer)) {
                log.warn("时间转换失败，字段值为{}", value);
                continue;
            }

            String format = DEFAULT_TIME_FORMAT;
            if (fields.length == 2) {
                format = fields[1];
            }

            try {
                String time = TimeUtil.formatTimestamp((Integer) value, format);
                result.put(fields[0] + SUFFIX, time);
            } catch (Exception e) {
                log.warn("时间转换失败，字段值为{}", value);
                continue;
            }
        }

        return result;
    }

    private Object dealWithMoneyConvert(MoneyFormatter moneyFormatter, Object result) {
        if (moneyFormatter == null) {
            return result;
        }


        if (moneyFormatter.fields().length == 0) {
            return result;
        }

        return convert(result, moneyFormatter, this::dealWithMoneyConvertInternal);
    }

    private Map<String, Object> dealWithMoneyConvertInternal(MoneyFormatter moneyFormatter, Map<String, Object> result) {
        for (String field : moneyFormatter.fields()) {
            if (StrUtil.isBlank(field)) {
                continue;
            }
            Object value = result.get(field);
            if (value == null) {
                continue;
            }

            if(value instanceof String || value instanceof Long) {
                try {
                    value = Integer.valueOf((String) value);
                } catch (NumberFormatException e) {
                    log.warn("金额转换失败，字段值为{}", value);
                    continue;
                }
            }

            if (!(value instanceof Integer)) {
                log.warn("金额转换失败，字段值为{}", value);
                continue;
            }

            String yuan = MoneyUtil.fen2yuan((Integer) value);
            result.put(field + SUFFIX, yuan);
        }

        return result;
    }

    private Object dealWithEnumConvert(EnumFormatter enumFormatter, Object result) {
        if (enumFormatter == null) {
            return result;
        }

        if (StrUtil.isBlank(enumFormatter.field())) {
            return result;
        }

        if (!ClassUtil.isEnum(enumFormatter.enumClazz()) || ! (DescValueEnum.class.isAssignableFrom(enumFormatter.enumClazz()))) {
            log.warn("类型参数错误，参数值为{}", enumFormatter.enumClazz());
            return result;
        }

        return convert(result, enumFormatter, this::dealWithEnumConvertInternal);
    }

    private <T> Object convert(Object result, T theEnum, BiFunction<T, Map<String, Object>, Map<String, Object>> function) {
        if (result == null) {
            return null;
        }
        if (result instanceof Collection) {
            ArrayList<Object> list = new ArrayList<>(((Collection) result).size());
            for (Object item : (Collection) result) {
                list.add(function.apply(theEnum, toMap(item)));
            }
            return list;
        }

        if (result instanceof Page) {
            List records = ((Page) result).getRecords();
            ((Page) result).setRecords((List) convert(records, theEnum, function));
            return result;
        }

        if (result instanceof Response) {
            Object data = ((Response) result).getData();
            Object convert = convert(data, theEnum, function);
            ((Response) result).setData(convert);
            return result;
        }

        return function.apply(theEnum, toMap(result));
    }

    private Map<String, Object> toMap(Object obj) {
        if (obj instanceof Map) {
            return (Map<String, Object>) obj;
        }
        return BeanUtil.beanToMap(obj);
    }

    private Map<String, Object> dealWithEnumConvertInternal(EnumFormatter enumFormatter, Map<String, Object> result) {
        Object value = result.get(enumFormatter.field());
        if (value == null) {
            return result;
        }

        DescValueEnum descValueEnum = DescValueEnum.getByValue(value, enumFormatter.enumClazz());
        if (descValueEnum == null) {
            return result;
        }

        result.put(enumFormatter.field() + SUFFIX, descValueEnum.getDesc());

        return result;
    }

    private Object dealWithEnumConverts(EnumFormatters enumFormatters, Object result) {
        if (enumFormatters == null) {
            return result;
        }
        EnumFormatter[] value = enumFormatters.value();
        for (EnumFormatter enumFormatter : value) {
            result = dealWithEnumConvert(enumFormatter, result);
        }
        return result;
    }


}
